#ifndef A8_PYENGINE_H
#define A8_PYENGINE_H

#include <Python.h>
#include <stdlib.h>
#include <stdio.h>
#include <a8/xvalue.hpp>
#include <a8/compat.hpp>
#include <a8/delegate.hpp>

namespace a8
{
	typedef std::pair<bool, a8::XValue> ScriptRetVal;
	static std::string getPythonError();
    class PyScript
    {
	private:
		bool mErrOccured;
    public:
        static const int MAX_ARGNUM = 8;

        PyScript(PyObject* m):mErrOccured(false)
        {
            assert(m);
            mModule = m;
        }
    
        virtual ~PyScript()
        {
        }

		bool ErrOccured()
		{
			return mErrOccured;
		}

		std::string GetErrorMsg()
		{
			return getPythonError();
		}

		bool Reload()
		{
			assert(mModule);
			mModule = PyImport_ReloadModule(mModule);
			if(!mModule){
#if 1
				printf("reload error:%s\n", getPythonError().c_str());				
#endif
			}
			return mModule != NULL;
		}

        ScriptRetVal CallFunc(const char* funcName)
        {
            ScriptRetVal result;
            CallPyFunc(funcName, NULL, 0, result);
            return result;
        }

        template<typename _T0_>
        ScriptRetVal CallFunc(const char* funcName, const _T0_& t0)
        {
            ScriptRetVal result;
            a8::XValue args[] = {a8::XValue(t0)};
            CallPyFunc(funcName, args, 1, result);
            return result;
        }

        template<typename _T0_,
                 typename _T1_>
        ScriptRetVal CallFunc(const char* funcName, const _T0_& t0,
							  const _T1_& t1)
        {
            ScriptRetVal result;
            a8::XValue args[] = {a8::XValue(t0), a8::XValue(t1)};
            CallPyFunc(funcName, args, 2, result);
            return result;
        }
    
        template<typename _T0_,
                 typename _T1_,
                 typename _T2_>
        ScriptRetVal CallFunc(const char* funcName, const _T0_& t0,
							  const _T1_& t1,
							  const _T2_& t2)
        {
            ScriptRetVal result;
            a8::XValue args[] = {a8::XValue(t0), a8::XValue(t1), a8::XValue(t2)};
            CallPyFunc(funcName, args, 3, result);
            return result;
        }


        template<typename _T0_,
                 typename _T1_,
                 typename _T2_,
                 typename _T3_>
        ScriptRetVal CallFunc(const char* funcName, const _T0_& t0,
							  const _T1_& t1,
							  const _T2_& t2,
							  const _T3_& t3)
        {
            ScriptRetVal result;
            a8::XValue args[] = {a8::XValue(t0), a8::XValue(t1), a8::XValue(t2), a8::XValue(t3)};
            CallPyFunc(funcName, args, 4, result);
            return result;
        }

        template<typename _T0_,
                 typename _T1_,
                 typename _T2_,
                 typename _T3_,
                 typename _T4_>
        ScriptRetVal CallFunc(const char* funcName, const _T0_& t0,
							  const _T1_& t1,
							  const _T2_& t2,
							  const _T3_& t3,
							  const _T4_& t4)
        {
            ScriptRetVal result;
            a8::XValue args[] = {a8::XValue(t0), a8::XValue(t1), a8::XValue(t2), a8::XValue(t3), a8::XValue(t4)};
            CallPyFunc(funcName, args, 5, result);
            return result;
        }
	     
        template<typename _T0_,
                 typename _T1_,
                 typename _T2_,
                 typename _T3_,
                 typename _T4_,
                 typename _T5_>
        ScriptRetVal CallFunc(const char* funcName, const _T0_& t0,
							  const _T1_& t1,
							  const _T2_& t2,
							  const _T3_& t3,
							  const _T4_& t4,
							  const _T5_& t5)
        {
            ScriptRetVal result;
            a8::XValue args[] = {a8::XValue(t0), a8::XValue(t1), a8::XValue(t2), a8::XValue(t3), a8::XValue(t4),
                                 a8::XValue(t5)};
            CallPyFunc(funcName, args, 6, result);
            return result;
        }

        template<typename _T0_,
                 typename _T1_,
                 typename _T2_,
                 typename _T3_,
                 typename _T4_,
                 typename _T5_,
                 typename _T6_>
        ScriptRetVal CallFunc(const char* funcName, const _T0_& t0,
							  const _T1_& t1,
							  const _T2_& t2,
							  const _T3_& t3,
							  const _T4_& t4,
							  const _T5_& t5,
							  const _T6_& t6)
        {
            ScriptRetVal result;
            a8::XValue args[] = {a8::XValue(t0), a8::XValue(t1), a8::XValue(t2), a8::XValue(t3), a8::XValue(t4),
                                 a8::XValue(t5), a8::XValue(t6)};
            return result;
        }

        template<typename _T0_,
                 typename _T1_,
                 typename _T2_,
                 typename _T3_,
                 typename _T4_,
                 typename _T5_,
                 typename _T6_,
                 typename _T7_>
        ScriptRetVal CallFunc(const char* funcName, const _T0_& t0,
							  const _T1_& t1,
							  const _T2_& t2,
							  const _T3_& t3,
							  const _T4_& t4,
							  const _T5_& t5,
							  const _T6_& t6,
							  const _T7_& t7)
        {
            ScriptRetVal result;
            a8::XValue args[] = {a8::XValue(t0), a8::XValue(t1), a8::XValue(t2), a8::XValue(t3), a8::XValue(t4),
                                 a8::XValue(t5), a8::XValue(t6), a8::XValue(t7)};
            return result;
        }

    private:
        PyObject *mModule;

        void CallPyFunc(const char* funcName, a8::XValue* args, int argNum, ScriptRetVal& result)
		{
			result.first = false;
			mErrOccured = false;
			PyObject *pyFunc = PyObject_GetAttrString(mModule, funcName);
			if(!pyFunc){
				mErrOccured = true;
				return;
			}
			PyObject *pyObj = NULL;
			char fmtBuf[MAX_ARGNUM + 1];
			char tmpBuf[MAX_ARGNUM][64]; 
			getArgsFormatDesc(fmtBuf, args, argNum);
			switch(argNum){
			case 0:
				pyObj = PyObject_CallFunction(pyFunc, fmtBuf);
				break;
			case 1:
				pyObj = PyObject_CallFunction(pyFunc, fmtBuf, 
											  XValueToPyArg(args[0], tmpBuf[0]));
				break;
			case 2:
				pyObj = PyObject_CallFunction(pyFunc, fmtBuf, 
											  XValueToPyArg(args[0], tmpBuf[0]),
											  XValueToPyArg(args[1], tmpBuf[1]));
				break;
			case 3:
				pyObj = PyObject_CallFunction(pyFunc, fmtBuf,
											  XValueToPyArg(args[0], tmpBuf[0]),
											  XValueToPyArg(args[1], tmpBuf[1]),
											  XValueToPyArg(args[2], tmpBuf[2]));
				break;
			case 4:
				pyObj = PyObject_CallFunction(pyFunc, fmtBuf,
											  XValueToPyArg(args[0], tmpBuf[0]),
											  XValueToPyArg(args[1], tmpBuf[1]),
											  XValueToPyArg(args[2], tmpBuf[2]),
											  XValueToPyArg(args[3], tmpBuf[3]));
				break;
			case 5:
				pyObj = PyObject_CallFunction(pyFunc, fmtBuf,
											  XValueToPyArg(args[0], tmpBuf[0]),
											  XValueToPyArg(args[1], tmpBuf[1]),
											  XValueToPyArg(args[2], tmpBuf[2]),
											  XValueToPyArg(args[3], tmpBuf[3]),
											  XValueToPyArg(args[4], tmpBuf[4]));
				break;
			case 6:
				pyObj = PyObject_CallFunction(pyFunc, fmtBuf,
											  XValueToPyArg(args[0], tmpBuf[0]),
											  XValueToPyArg(args[1], tmpBuf[1]),
											  XValueToPyArg(args[2], tmpBuf[2]),
											  XValueToPyArg(args[3], tmpBuf[3]),
											  XValueToPyArg(args[4], tmpBuf[4]),
											  XValueToPyArg(args[5], tmpBuf[5]));
				break;
			case 7:
				pyObj = PyObject_CallFunction(pyFunc, fmtBuf,
											  XValueToPyArg(args[0], tmpBuf[0]),
											  XValueToPyArg(args[1], tmpBuf[1]),
											  XValueToPyArg(args[2], tmpBuf[2]),
											  XValueToPyArg(args[3], tmpBuf[3]),
											  XValueToPyArg(args[4], tmpBuf[4]),
											  XValueToPyArg(args[5], tmpBuf[5]),
											  XValueToPyArg(args[6], tmpBuf[6]));
				break;
			case 8:
				pyObj = PyObject_CallFunction(pyFunc, fmtBuf,
											  XValueToPyArg(args[0], tmpBuf[0]),
											  XValueToPyArg(args[1], tmpBuf[1]),
											  XValueToPyArg(args[2], tmpBuf[2]),
											  XValueToPyArg(args[3], tmpBuf[3]),
											  XValueToPyArg(args[4], tmpBuf[4]),
											  XValueToPyArg(args[5], tmpBuf[5]),
											  XValueToPyArg(args[6], tmpBuf[6]),
											  XValueToPyArg(args[7], tmpBuf[7]));
				break;
			}
			if(!pyObj){
				result.first = false;
				mErrOccured = true;
			}else{
				result.first = true;
				PyObjToXValue(pyObj, result.second);
				Py_DECREF(pyObj);
			}
			Py_DECREF(pyFunc);
		}

        static void getArgsFormatDesc(char* fmtBuf, a8::XValue* args, int argNum)
        {
            fmtBuf[argNum] = '\0';
            for(int i = 0; i < argNum; i++){
                switch(args[i].type()){
                case XVT_INT:
                    fmtBuf[i] = 'i';
                    break;
                case XVT_UINT:
                    fmtBuf[i] = 'i';
                    break;
                case XVT_INT64:
                    fmtBuf[i] = 's';
                    break;
                case XVT_UINT64:
                    fmtBuf[i] = 's';
                    break;
                case XVT_STRING:
                    fmtBuf[i] = 's';
                    break;
                default:
                    fmtBuf[i] = '\0';
                }
            }
        }

        static long XValueToPyArg(a8::XValue& arg, char* buf)
        {
            switch(arg.type()){
            case XVT_INT:
            case XVT_UINT:
                return arg.GetInt();
                break;
            case XVT_INT64:
                {
                    XItoa(arg.GetInt64(), buf, 64, 10);
                    return long(buf);
                }
                break;
            case XVT_UINT64:
                {
                    XItoa(arg.GetUInt64(), buf, 64, 10);
                    return (long)buf;
                }
                break;
            case XVT_STRING:
                {
                    return (long)arg.GetString().c_str();
                }
                break;
            defulat:
                return 0;
            }
            return 0;
        }

		static bool PyObjToXValue(PyObject* pyobj, a8::XValue& xvalue)
		{
			if(PyInt_Check(pyobj)){
				xvalue.Set((long long)PyInt_AsLong(pyobj));
			}else if(PyFloat_Check(pyobj)){
				xvalue.Set(PyFloat_AsDouble(pyobj));
			}else if(PyString_Check(pyobj)){
				xvalue.Set((const char*)PyString_AsString(pyobj));
			}
			return true;
		}

    };

    class PyEngine
    {
    public:
        PyEngine()
        {
        }
        ~PyEngine()
        {
        }

        void Init()
        {
            Py_Initialize();
			PyRun_SimpleString("import sys");
			PyRun_SimpleString("sys.path.append('./')");
        }

        void UnInit()
        {
            Py_Finalize();
        }

        int IsInitialized()
        {
            return Py_IsInitialized();
        }

        int RegisterModule(const char* module, PyMethodDef *methods)
        {
            return Py_InitModule(module, methods) != 0;
        }

        PyScript* ImportModule(const char* module)
		{
			PyObject *p = PyImport_ImportModule(module);
			if(p){
				return new PyScript(p);
			}else{
#if 1
				printf("import %s error:%s\n", module, getPythonError().c_str());
#endif
				return NULL;
			}
		}

        int RunSimpleString(const char* szString)
        {
            PyRun_SimpleString(szString);
            return 1;
        }

        void PrintLastError()
        {
            PyErr_Print();
        }
    };

	static std::string getPythonError()
	{
		std::string strErrorMsg;
		if(PyErr_Occurred() == NULL){
			return strErrorMsg;
		}
		PyObject *type_obj, *value_obj, *traceback_obj;
		PyErr_Fetch(&type_obj, &value_obj, &traceback_obj);			
		if(value_obj == NULL){
			return strErrorMsg;
		}
		PyErr_NormalizeException(&type_obj, &value_obj, 0);
		if (PyString_Check(PyObject_Str(value_obj))){
			strErrorMsg = PyString_AsString(PyObject_Str(value_obj));
		}
		if(traceback_obj){
			strErrorMsg += "traceback:";;

			PyObject *pModuleName = PyString_FromString("traceback");
			PyObject * pTraceModule = PyImport_Import(pModuleName);
			Py_XDECREF(pModuleName);
			if (pTraceModule != NULL){
				PyObject * pModuleDict = PyModule_GetDict(pTraceModule);
				if (pModuleDict != NULL){
					PyObject * pFunc = PyDict_GetItemString(pModuleDict,"format_exception");
					if (pFunc != NULL){
						PyObject * errList = PyObject_CallFunctionObjArgs(pFunc,type_obj,value_obj, traceback_obj,NULL);
						if (errList != NULL){
							int listSize = PyList_Size(errList);
							for (int i=0;i < listSize;++i){
								strErrorMsg += PyString_AsString(PyList_GetItem(errList,i));
							}
						}
					}
				}
				Py_XDECREF(pTraceModule);
			}				
		}//end if

		if(type_obj){
			Py_XDECREF(type_obj);
		}
		if(value_obj){
			Py_XDECREF(value_obj);
		}
		if(traceback_obj){
			Py_XDECREF(traceback_obj);			
		}
	}

}

#endif

